package com.loe.json_ext

import org.json.JSONArray
import org.json.JSONObject
import java.lang.StringBuilder
import java.lang.reflect.Constructor
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import kotlin.reflect.KClass

/**
 * 安全实例化Json对象
 */
fun Json(jsonString: String? = null): JSONObject
{
    try
    {
        return JSONObject(jsonString)
    } catch (e: Exception)
    {
    }
    return JSONObject()
}

fun Json(key: String, value: Any): JSONObject
{
    return JSONObject().put(key, value)
}

/**
 * 安全实例化JsonArray对象
 */
fun JsonArray(jsString: String? = null): JSONArray
{
    try
    {
        return JSONArray(jsString)
    } catch (e: Exception)
    {
    }
    return JSONArray()
}

fun <T> jsOf(vararg elements: T): JSONArray
{
    val js = JsonArray()
    elements.forEach()
    {
        try
        {
            js.put(it)
        } catch (e: Exception)
        {
        }
    }
    return js
}

fun JSONObject?.isEmpty(): Boolean = if(this == null) true else !keys().hasNext()
fun JSONArray?.isEmpty(): Boolean = if(this == null) true else length() == 0

/**  Json安全取值，无null */

fun JSONObject?.gotString(key: String, default: String = ""): String
{
    val s = this?.optString(key, default)
    if (s == "null") return default
    return s ?: default
}

fun JSONObject?.gotInt(key: String, default: Int = 0): Int
{
    return this?.optInt(key, default) ?: default
}

fun JSONObject?.gotLong(key: String, default: Long = 0): Long
{
    return this?.optLong(key, default) ?: default
}

fun JSONObject?.gotDouble(key: String, default: Double = 0.0): Double
{
    return this?.optDouble(key, default) ?: default
}

fun JSONObject?.gotBoolean(key: String, default: Boolean = false): Boolean
{
    if(this == null || !has(key)) return default
    return optBoolean(key) || gotInt(key) > 0
}

fun JSONObject?.gotDoubleString(key: String, default: String? = null): String
{
    val d = gotDouble(key)
    if (default != null && d == 0.0) return default
    val l = d.toLong()
    return if (d - l == 0.0) l.toString() else d.toString()
}

fun JSONObject?.gotJson(key: String): JSONObject
{
    var o = this?.optJSONObject(key)
    try
    {
        if (this != null && o == null) o = JSONObject(optString(key))
    } catch (e: Exception)
    {
    }
    return o ?: JSONObject()
}

fun JSONObject?.gotArray(key: String): JSONArray
{
    var js = this?.optJSONArray(key)
    try
    {
        if (this != null && js == null) js = JSONArray(optString(key))
    } catch (e: Exception)
    {
    }
    return js ?: JSONArray()
}

/** JsonArray安全取值，无null */

fun JSONArray?.gotString(i: Int, default: String = ""): String
{
    val s = this?.optString(i, default)
    if (s == "null") return default
    return s ?: default
}

fun JSONArray?.gotInt(i: Int, default: Int = 0): Int
{
    return this?.optInt(i, default) ?: default
}

fun JSONArray?.gotLong(i: Int, default: Long = 0): Long
{
    return this?.optLong(i, default) ?: default
}

fun JSONArray?.gotDouble(i: Int, default: Double = 0.0): Double
{
    return this?.optDouble(i, default) ?: default
}

fun JSONArray?.gotBoolean(i: Int, default: Boolean = false): Boolean
{
    if (gotInt(i) > 0) return true
    return this?.optBoolean(i, default) ?: default
}

fun JSONArray?.gotDoubleString(i: Int, default: String? = null): String
{
    val d = gotDouble(i)
    if (default != null && d == 0.0) return default
    val l = d.toLong()
    return if (d - l == 0.0) l.toString() else d.toString()
}

fun JSONArray?.gotJson(i: Int): JSONObject
{
    var o = this?.optJSONObject(i)
    try
    {
        if (this != null && o == null) o = JSONObject(optString(i))
    } catch (e: Exception)
    {
    }
    return o ?: JSONObject()
}

fun JSONArray?.gotArray(i: Int): JSONArray
{
    var js = this?.optJSONArray(i)
    try
    {
        if (this != null && js == null) js = JSONArray(optString(i))
    } catch (e: Exception)
    {
    }
    return js ?: JSONArray()
}

/** JsonArray遍历 */

inline fun <T> JSONArray?.forEach(action: (T) -> Unit)
{
    if (this != null) for (i in 0 until length()) action(get(i) as T)
}

inline fun <T> JSONArray?.forEachIndex(action: (i: Int, T) -> Unit)
{
    if (this != null) for (i in 0 until length()) action(i, get(i) as T)
}

inline fun <T> JSONArray?.forEachReverse(action: (T) -> Unit)
{
    if (this != null) for (i in length() - 1 downTo 0) action(get(i) as T)
}

inline fun <T> JSONArray?.forEachIndexReverse(action: (i: Int, T) -> Unit)
{
    if (this != null) for (i in length() - 1 downTo 0) action(i, get(i) as T)
}

inline fun <T> JSONArray?.map(action: (T) -> Any): JSONArray
{
    val js = JSONArray()
    if (this != null) for (i in 0 until length())
    {
        js.put(action(get(i) as T))
    }
    return js
}

fun JSONArray?.map(name: String): JSONArray
{
    val js = JSONArray()
    if (this != null) for (i in 0 until length())
    {
        js.put(gotJson(i).get(name))
    }
    return js
}

inline fun <T> JSONArray?.mapNotNull(action: (T) -> Any?): JSONArray
{
    val js = JSONArray()
    if (this != null) for (i in 0 until length())
    {
        val o = action(get(i) as T)
        if (o != null) js.put(o)
    }
    return js
}

inline fun <T> JSONArray?.filter(action: (T) -> Boolean): JSONArray
{
    val js = JSONArray()
    if (this != null) for (i in 0 until length())
    {
        val o = get(i) as T
        if (action(o)) js.put(o)
    }
    return js
}

inline fun <reified T : Any> JSONArray?.joinToString(separator: CharSequence = ",", noinline action: (T) -> CharSequence): String
{
    return toList<T>().joinToString(separator, transform = action)
}

fun JSONArray?.joinToString(separator: CharSequence = ","): String
{
    return toList<String>().joinToString(separator)
}

/** 对象转Json */
fun Any?.toJsonString(indentSpaces: Int): String = toJson().toString(indentSpaces)
fun Any?.toJsonString(): String = toJson().toString()
fun Any?.toJson(): JSONObject
{
    if (this is JSONObject)
    {
        return this
    }
    var json = JSONObject()
    if (this == null) return json

    try
    {
        val rootJsonField = this.javaClass.getDeclaredField("rootJson")
        rootJsonField.isAccessible = true
        json = rootJsonField.get(this) as JSONObject
    } catch (e: Exception)
    {
    }

    this.javaClass.declaredFields.forEach()
    { field ->
        field.isAccessible = true
        try
        {
            val annotationName = field.getAnnotation(JsonName::class.java)?.value
            val name = annotationName ?: field.name
            val cls = field.type.kotlin
            if (isBean(cls))
            {
                if (cls == JSONObject::class && name == "rootJson")
                {
                    return@forEach
                }
                if(cls == JSONArray::class)
                {
                    json.put(name, field.get(this))
                }else if (field.type.simpleName.contains("List"))
                {
                    json.put(name, (field.get(this) as List<*>).toJsonArray())
                } else
                {
                    json.put(name, field.get(this).toJson())
                }
            } else
            {
                json.put(name, field.get(this))
            }
        } catch (e: Exception)
        {
            e.printStackTrace()
        }
    }
    for (method in this.javaClass.declaredMethods)
    {
        if (method.name == "out")
        {
            try
            {
                method.isAccessible = true
                return method.invoke(this, json) as JSONObject
            } catch (e: Exception)
            {
            }
        }
    }
    return json
}

fun List<*>?.toJsonArrayString(indentSpaces: Int): String = toJsonArray().toString(indentSpaces)
fun List<*>?.toJsonArrayString(): String = toJsonArray().toString()
fun List<*>?.toJsonArray(): JSONArray
{
    if (this is JSONArray)
    {
        return this
    }
    val js = JSONArray()
    if (this == null) return js
    forEach()
    {
        if (it != null)
        {
            val cls = it::class
            if (isBean(cls))
            {
                when
                {
                    cls.java.simpleName.contains("Array") ->
                    {
                        js.put(it)
                    }
                    cls.java.simpleName.contains("List") ->
                    {
                        js.put((it as List<*>).toJsonArray())
                    }
                    else ->
                    {
                        js.put(it.toJson())
                    }
                }
            } else
            {
                js.put(it)
            }
        } else js.put(null)
    }
    return js
}

/** Json转对象 */

inline fun <reified T : Any> JSONObject?.toBean(): T
{
    return toBean(T::class.java.create() as T)
}

fun <T : Any> JSONObject?.toBean(o: T): T
{
    if (this == null) return o
    o::class.java.declaredFields.forEach()
    { field ->
        field.isAccessible = true
        try
        {
            val annotationName = field.getAnnotation(JsonName::class.java)?.value
            val fieldName = annotationName ?: field.name
            val cls = field.type.kotlin
            if (cls == JSONObject::class && fieldName == "rootJson")
            {
                field.set(o, this)
                return@forEach
            }
            if (has(fieldName)) when (cls)
            {
                String::class ->
                {
                    field.set(o, gotString(fieldName))
                }
                Int::class ->
                {
                    field.set(o, gotInt(fieldName))
                }
                Long::class ->
                {
                    field.set(o, gotLong(fieldName))
                }
                Boolean::class ->
                {
                    if (gotInt(fieldName) > 0)
                    {
                        field.set(o, true)
                    } else
                    {
                        field.set(o, gotBoolean(fieldName))
                    }
                }
                Double::class ->
                {
                    field.set(o, gotDouble(fieldName))
                }
                Float::class ->
                {
                    field.set(o, gotDouble(fieldName).toFloat())
                }
                JSONArray::class ->
                {
                    field.set(o, gotArray(fieldName))
                }
                JSONObject::class ->
                {
                    field.set(o, gotJson(fieldName))
                }
                else ->
                {
                    if (cls.java.simpleName.contains("List"))
                    {
                        field.set(
                            o,
                            gotArray(fieldName).toList((field.genericType as ParameterizedType).actualTypeArguments[0])
                        )
                    } else
                    {
                        field.set(o, gotJson(fieldName).toBean(field.type.newInstance()))
                    }
                }
            }
        } catch (e: Exception)
        {
            e.printStackTrace()
        }
    }
    o::class.java.declaredMethods.forEach()
    { method ->
        if (method.name == "init")
        {
            try
            {
                method.isAccessible = true
                method.invoke(o, this)

                return@forEach
            } catch (e: Exception)
            {
            }
        }
    }
    return o
}

inline fun <reified T : Any> String?.toBean(): T
{
    return Json(this).toBean(T::class.java.create() as T)
}

fun <T : Any> String?.toBean(o: T): T
{
    return Json(this).toBean(o)
}

inline fun <reified T : Any> JSONArray?.toList(): ArrayList<T>
{
    return toList(T::class.java) as ArrayList<T>
}

fun JSONArray?.toList(type: Type): ArrayList<*>
{
    val list = ArrayList<Any>()
    if (this == null) return list
    var isList = false
    var isBean = false
    var isJson = false
    if (type is ParameterizedType)
    {
        isBean = true
        isList = true
    } else
    {
        val cls = (type as Class<*>).kotlin

        isJson = cls == JSONObject::class || cls == JSONArray::class

        if (!isJson)
        {
            isBean = isBean(cls)
            isList = type.simpleName.contains("List")
        }
    }
    for (i in 0 until length())
    {
        if (isJson)
        {
            list.add(opt(i))
        } else if (isBean)
        {
            if (isList)
            {
                list.add(optJSONArray(i).toList((type as ParameterizedType).actualTypeArguments[0]))
            } else
            {
                val o = (type as Class<*>).create()
                list.add(optJSONObject(i).toBean(o))
            }
        } else
        {
            list.add(opt(i))
        }
    }
    return list
}

inline fun <reified T : Any> String?.toList(): ArrayList<T>
{
    return JsonArray(this).toList(T::class.java) as ArrayList<T>
}

fun String?.toList(type: Type): ArrayList<*>
{
    return JsonArray(this).toList(type)
}

private fun isBean(cls: KClass<*>): Boolean
{
//    return when (cls)
//    {
//        String::class,
//        Int::class,
//        Long::class,
//        Boolean::class,
//        Double::class,
//        Float::class -> false
//        else -> true
//    }
    if(cls == String::class)
    {
        return false
    }
    return cls.javaPrimitiveType == null
}

fun <T : Any> Class<T>.create(): Any
{
    if (simpleName.contains("List"))
    {
        return if (simpleName.length == 4)
        {
            ArrayList<T>()
        } else
        {
            newInstance()
        }
    }
    when (this.kotlin)
    {
        String::class -> return ""
        Int::class -> return 0
        Long::class -> return 0L
        Boolean::class -> return false
        Double::class -> return 0.0
        Float::class -> return 0f
        else ->
        {
            var constructor: Constructor<*> = constructors[0]
            constructors.forEach()
            {
                if (it.parameterTypes.size < constructor.parameterTypes.size)
                {
                    constructor = it
                }
            }
            val parameterTypes = constructor.parameterTypes
            if (parameterTypes.isEmpty())
            {
                return newInstance()
            }
            val pv = parameterTypes.map()
            {
                it.create()
            }.toTypedArray()
            return constructor.newInstance(*pv)
        }
    }
}

operator fun JSONObject?.plus(json: JSONObject?): JSONObject
{
    if(this == null) return json ?: Json()
    json?.keys()?.forEach()
    { key ->
        put(key, json.get(key))
    }
    return this
}

operator fun JSONArray?.plus(js: JSONArray?): JSONArray
{
    if(this == null) return js ?: JSONArray()
    js?.forEach<Any>
    {
        put(it)
    }
    return this
}

/**
 * Name注解
 */
@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
annotation class JsonName(val value: String)